/*
 * Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include "FleeingMovementGenerator.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "MapManager.h"
#include "MoveSpline.h"
#include "MoveSplineInit.h"
#include "ObjectAccessor.h"
#include "VMapFactory.h"

#define MIN_QUIET_DISTANCE 28.0f
#define MAX_QUIET_DISTANCE 43.0f

template<class T>
void FleeingMovementGenerator<T>::DoInitialize(T& owner)
{
    owner.SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING);
    owner.AddUnitState(UNIT_STATE_FLEEING);

    _path = nullptr;
    SetTargetLocation(owner);
}

template<class T>
void FleeingMovementGenerator<T>::DoFinalize(T&)
{
}

template<>
void FleeingMovementGenerator<Player>::DoFinalize(Player& owner)
{
    owner.RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING);
    owner.ClearUnitState(UNIT_STATE_FLEEING);
    owner.StopMoving();
}

template<>
void FleeingMovementGenerator<Creature>::DoFinalize(Creature& owner)
{
    owner.RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING);
    owner.ClearUnitState(UNIT_STATE_FLEEING | UNIT_STATE_FLEEING_MOVE);
    if (owner.getVictim())
        owner.SetTarget(owner.getVictim()->GetGUID());
}

template<class T>
void FleeingMovementGenerator<T>::DoReset(T &owner)
{
    DoInitialize(owner);
}

template<class T>
bool FleeingMovementGenerator<T>::DoUpdate(T& owner, uint32 diff)
{
    if (!owner.IsAlive())
        return false;

    if (owner.HasUnitState(UNIT_STATE_NOT_MOVE) || owner.IsMovementPreventedByCasting())
    {
        _interrupt = true;
        owner.StopMoving();
        _path = nullptr;
        return true;
    }
    else
        _interrupt = false;

    _timer.Update(diff);
    if (!_interrupt && _timer.Passed() && owner.movespline->Finalized())
        SetTargetLocation(owner);

    return true;
}

template<class T>
void FleeingMovementGenerator<T>::SetTargetLocation(T& owner)
{
    if (owner.HasUnitState(UNIT_STATE_NOT_MOVE) || owner.IsMovementPreventedByCasting())
    {
        _interrupt = true;
        owner.StopMoving();
        _path = nullptr;
        return;
    }

    owner.AddUnitState(UNIT_STATE_FLEEING_MOVE);

    Position destination = owner.GetPosition();
    GetPoint(owner, destination);

    // Add LOS check for target point
    if (!owner.IsWithinLOS(destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ()))
    {
        _timer.Reset(200);
        return;
    }

    if (!_path)
    {
        _path = std::make_unique<PathGenerator>(&owner);
        _path->SetPathLengthLimit(30.0f);
    }

    bool result = _path->CalculatePath(destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ());
    if (!result || (_path->GetPathType() & PATHFIND_NOPATH)
        || (_path->GetPathType() & PATHFIND_SHORTCUT)
        || (_path->GetPathType() & PATHFIND_FARFROMPOLY))
    {
        _timer.Reset(100);
        return;
    }

    Movement::MoveSplineInit init(owner);
    init.MovebyPath(_path->GetPath());
    init.SetWalk(false);
    int32 traveltime = init.Launch();
    _timer.Reset(traveltime + urand(800, 1500));
}

template<class T>
void FleeingMovementGenerator<T>::GetPoint(T& owner, Position& position)
{
    float casterDistance, casterAngle;
    if (Unit* fleeTarget = ObjectAccessor::GetUnit(owner, _fleeTargetGUID))
    {
        casterDistance = fleeTarget->GetDistance(owner);
        if (casterDistance > 0.2f)
            casterAngle = fleeTarget->GetAngle(&owner);
        else
            casterAngle = frand(0.0f, 2.0f * float(M_PI));
    }
    else
    {
        casterDistance = 0.0f;
        casterAngle = frand(0.0f, 2.0f * float(M_PI));
    }

    float distance, angle;
    if (casterDistance < MIN_QUIET_DISTANCE)
    {
        distance = frand(0.4f, 1.3f) * (MIN_QUIET_DISTANCE - casterDistance);
        angle = casterAngle + frand(-float(M_PI) / 8.0f, float(M_PI) / 8.0f);
    }
    else if (casterDistance > MAX_QUIET_DISTANCE)
    {
        distance = frand(0.4f, 1.0f) * (MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE);
        angle = -casterAngle + frand(-float(M_PI) / 4.0f, float(M_PI) / 4.0f);
    }
    else // we are inside quiet range
    {
        distance = frand(0.6f, 1.2f) * (MAX_QUIET_DISTANCE - MIN_QUIET_DISTANCE);
        angle = frand(0.0f, 2.0f * float(M_PI));
    }

    owner.MovePositionToFirstCollision(position, distance, angle);
}

template void FleeingMovementGenerator<Player>::DoInitialize(Player&);
template void FleeingMovementGenerator<Creature>::DoInitialize(Creature&);
template void FleeingMovementGenerator<Player>::DoReset(Player&);
template void FleeingMovementGenerator<Creature>::DoReset(Creature&);
template bool FleeingMovementGenerator<Player>::DoUpdate(Player&, uint32);
template bool FleeingMovementGenerator<Creature>::DoUpdate(Creature&, uint32);
template void FleeingMovementGenerator<Player>::SetTargetLocation(Player&);
template void FleeingMovementGenerator<Creature>::SetTargetLocation(Creature&);
template void FleeingMovementGenerator<Player>::GetPoint(Player&, Position&);
template void FleeingMovementGenerator<Creature>::GetPoint(Creature&, Position&);

//---- TimedFleeingMovementGenerator

bool TimedFleeingMovementGenerator::Update(Unit& owner, uint32 time_diff)
{
    if (!owner.IsAlive())
        return false;

    _totalFleeTime.Update(time_diff);
    if (_totalFleeTime.Passed())
        return false;

    // This calls grant-parent Update method hiden by FleeingMovementGenerator::Update(Creature &, const uint32 &) version
    // This is done instead of casting Unit& to Creature& and call parent method, then we can use Unit directly
    return MovementGeneratorMedium<Creature, FleeingMovementGenerator<Creature>>::Update(owner, time_diff);
}

void TimedFleeingMovementGenerator::Finalize(Unit& owner)
{
    owner.RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_FLEEING);
    owner.ClearUnitState(UNIT_STATE_FLEEING);
    owner.StopMoving();
    if (Unit* victim = owner.getVictim())
    {
        if (owner.IsAlive())
        {
            owner.AttackStop();
            owner.ToCreature()->AI()->AttackStart(victim);
        }
    }
}
